package com.esunny.springboot.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * zookeeper的分布式锁
 */
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {
    private ZooKeeper zooKeeper;
    private String Znode;

    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper("localhost:2181", 10000, this);//超时时间为10秒
    }

    public boolean getLock(String businessCode) throws KeeperException, InterruptedException {
        //判断业务根节点，不添加监听器
        Stat stat = zooKeeper.exists("/" + businessCode, false);
        if (stat == null) {
            //参数分别为节点，节点数据，节点权限（不需要用户名密码就能创建节点），持久节点
            zooKeeper.create("/" + businessCode, businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //创建瞬时有序节点,如果是第一个就类似于 /order/order_00001
        Znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        //获取此节点下的所有节点
        List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
        //让最小的节点获得锁
        Collections.sort(childrenNodes);
        String firstNode = childrenNodes.get(0);
        if (Znode.endsWith(firstNode)) {
            return true;
        }
        //不是第一个节点则监听前一个节点
        String lastNode = firstNode;
        for (String node : childrenNodes) {
            if (Znode.endsWith(node)) {
                //监听前一个节点
                zooKeeper.exists("/" + businessCode + "/" + lastNode, true);
                break;
            } else {
                lastNode = node;
            }

        }
        //让不是第一个节点的线程进入等待，因为它监听了前一个节点，前一个节点被删，会执行下面的process方法，唤醒这里，从而继续执行
        synchronized (this) {
            wait(3000);//为了避免前一个节点出问题而导致线程永久等待下去，超时自动唤醒
        }

        return true;
    }

    @Override
    public void close() throws Exception {
        //-1是删除所有的同名节点（上面没设置版本号）
        log.info(Thread.currentThread().getName() + "释放了锁");
        zooKeeper.delete(Znode, -1);
        zooKeeper.close();

    }

    /**
     * 监听事件
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        //前一个节点被删后，执行此方法，唤醒阻塞的线程
        if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
            synchronized (this) {
                notify();//唤醒当前对象的一个线程
            }
        }
    }
}
